# Copyright (c) 2015, 2019 Ruslan Baratov, Alexandre Pretyman
# All rights reserved.

cmake_minimum_required(VERSION 3.2)

### Input params check

string(COMPARE EQUAL "@multi_arch_install_root@" "" is_empty)
if(is_empty)
  message(FATAL_ERROR "multi_arch_install_root is empty")
endif()

string(COMPARE EQUAL "@ios_architectures@" "" is_empty)
if(is_empty)
  message(FATAL_ERROR "ios_architectures is empty")
endif()

string(COMPARE EQUAL "@HUNTER_PACKAGE_INSTALL_PREFIX@" "" is_empty)
if(is_empty)
  message(FATAL_ERROR "HUNTER_PACKAGE_INSTALL_PREFIX is empty")
endif()

set(unrelocatable "@HUNTER_PACKAGE_UNRELOCATABLE_TEXT_FILES@")

set(ios_architectures @ios_architectures@)
set(built_arch_roots)
foreach(x ${ios_architectures})
  list(APPEND built_arch_roots @multi_arch_install_root@/${x})
endforeach()
list(LENGTH ios_architectures total_arch_number)

# We work with the first root path: we move everything, except the *.a static
# library files into @HUNTER_PACKAGE_INSTALL_PREFIX@, then we lipo the static
# libraries together into @HUNTER_PACKAGE_INSTALL_PREFIX@
list(GET built_arch_roots 0 first_built_root)

file(GLOB_RECURSE
    header_files
    RELATIVE
      "${first_built_root}"
    ${first_built_root}/include/*
)

# Unify unrelocatable files
foreach(x ${unrelocatable})
  foreach(arch ${ios_architectures})
    set(y "@multi_arch_install_root@/${arch}/${x}")
    if(NOT EXISTS "${y}")
      message(FATAL_ERROR "File not found: ${y}")
    endif()
    file(READ "${y}" content)
    string(
        REPLACE
        "@multi_arch_install_root@/${arch}"
        "@HUNTER_PACKAGE_INSTALL_PREFIX@"
        content
        "${content}"
    )
    file(WRITE "${y}" "${content}")
  endforeach()
endforeach()

# Check unrelocatable files
foreach(x ${unrelocatable})
  set(etalon "${first_built_root}/${x}")
  foreach(arch ${ios_architectures})
    set(to_check "@multi_arch_install_root@/${arch}/${x}")
    file(
        DIFFERENT
        is_different
        FILES "${to_check}" "${etalon}"
    )
    if(is_different)
      message(FATAL_ERROR "Files differ: ${to_check} ${etalon}")
    endif()
  endforeach()
endforeach()

# Move unrelocatable files
foreach(x ${unrelocatable})
  set(etalon "${first_built_root}/${x}")
  set(dst "@HUNTER_PACKAGE_INSTALL_PREFIX@/${x}")
  file(WRITE "${dst}" "") # for creating missing directories
  file(RENAME "${etalon}" "${dst}")
  foreach(arch ${ios_architectures})
    file(REMOVE "@multi_arch_install_root@/${arch}/${x}")
  endforeach()
endforeach()

# The preprocessor macros below are from
# http://sourceforge.net/p/predef/wiki/Architectures/
# except armv7, which was taken from:
# clang -arch armv7 -dD -E config.h
function(preprocessor_macro arch result)
  if(${arch} STREQUAL "armv7")
    set(${result} "__ARM_ARCH_7A__" PARENT_SCOPE)
  elseif(${arch} STREQUAL "armv7s")
    set(${result} "__ARM_ARCH_7S__" PARENT_SCOPE)
  elseif(${arch} STREQUAL "arm64")
    set(${result} "__aarch64__" PARENT_SCOPE)
  elseif(${arch} STREQUAL "i386")
    set(${result} "__i386__" PARENT_SCOPE)
  elseif(${arch} STREQUAL "x86_64")
    set(${result} "__x86_64__" PARENT_SCOPE)
  else()
    message(FATAL_ERROR "Architecture: ${arch} is not supported")
  endif()
endfunction()

# List the different roots which ${file_name} differ from
# ${first_built_root} and store them in result
# If result is an empty list, all files are equal
set(built_arch_roots_except_first ${built_arch_roots})
list(REMOVE_AT built_arch_roots_except_first 0)
function(list_roots_file_diff file_name result)
  # make different_roots a list, so it can be printed for better investigation
  set(different_roots)
  foreach(built_arch_root ${built_arch_roots_except_first})
    file(DIFFERENT
        is_different
        FILES
          "${first_built_root}/${file_name}"
          "${built_arch_root}/${file_name}"
    )
    if(is_different)
      list(APPEND different_roots "${built_arch_root}/${file_name}")
    endif()
  endforeach()
  set(${result} ${different_roots} PARENT_SCOPE)
endfunction()

# Function is used when the files of all archs are the same, move
# one file to the final destination and delete the others.
function(move_one_delete_other file_name)
  file(RENAME
      "${first_built_root}/${file_name}"
      "@HUNTER_PACKAGE_INSTALL_PREFIX@/${file_name}"
  )
  # Remove the unneeded copies
  foreach(built_arch_root ${built_arch_roots_except_first})
    file(REMOVE "${built_arch_root}/${file_name}")
  endforeach()
endfunction()

# Merge a header file that differed in content when the project was built with
# different architectures. Guard its contents with #ifdef #elif to guarantee
# that each architecture gets only its file
function(merge_header_diff_archs file_name)
  set(merged_file_contents "
/******************************************************
 * File auto generated by Hunter by merging file:     *
 * ${file_name}
 * which differed its contents in a multi-arch build. *
 * The supported architectures are:                   *
 * ${ios_architectures}
 *****************************************************/\n"
  )
  set(arch_counter 1)
  foreach(arch ${ios_architectures})
    set(full_path_file_name
        "@multi_arch_install_root@/${arch}/${file_name}"
    )
    preprocessor_macro(${arch} arch_define)
    if (arch_counter EQUAL 1)
      set(merged_file_contents
          "${merged_file_contents}\n#ifdef ${arch_define}\n"
      )
    else()
      set(merged_file_contents
          "${merged_file_contents}\n#elif ${arch_define}\n"
      )
    endif()
    if(EXISTS "${full_path_file_name}")
      file(READ
          ${full_path_file_name}
          file_contents
      )
    else()
      set(file_contents "")
    endif()
    # Append contents to the merged file
    set(merged_file_contents
        "${merged_file_contents}${file_contents}"
    )
    # Discard the file
    file(REMOVE
        ${full_path_file_name}
    )
    if (${arch_counter} EQUAL ${total_arch_number})
      set(merged_file_contents
          "${merged_file_contents}
#else
# error Architecture not supported. It is not one of ${ios_architectures}
#endif\n"
      )
    endif()
    math(EXPR arch_counter ${arch_counter}+1)
  endforeach()
  file(WRITE
      "@HUNTER_PACKAGE_INSTALL_PREFIX@/${file_name}"
      "${merged_file_contents}"
  )
endfunction()

# Compare for differences between files in built_arch_roots
# If files are different and are in the include/ directory
# they are merged with #ifdef guards
foreach(file_name ${header_files})
  get_filename_component(final_dir
      "@HUNTER_PACKAGE_INSTALL_PREFIX@/${file_name}"
      DIRECTORY
  )
  file(MAKE_DIRECTORY ${final_dir})
  # List the arch roots where the file is different
  list_roots_file_diff(${file_name} different_roots)
  list(LENGTH different_roots len)
  if(${len} EQUAL 0)
    # Files are the same, so we move them to @HUNTER_PACKAGE_INSTALL_PREFIX@
    move_one_delete_other("${file_name}")
  else()
    # merge the file contents with #ifdef guards
    merge_header_diff_archs(${file_name})
  endif()
endforeach()

file(GLOB_RECURSE
    binary_lib_files
    RELATIVE
    "${first_built_root}"
    "${first_built_root}/lib/*.a"
    "${first_built_root}/lib/*.dylib"
)

file(GLOB_RECURSE
    binary_bin_files
    RELATIVE
      "${first_built_root}"
    ${first_built_root}/bin/*
)

# we lipo the libraries into @HUNTER_PACKAGE_INSTALL_PREFIX@
foreach(x ${binary_lib_files} ${binary_bin_files})
  # if the dir to put the library in, does not exist, then create it
  # this is needed or else lipo could fail
  get_filename_component(dir "@HUNTER_PACKAGE_INSTALL_PREFIX@/${x}" DIRECTORY)
  file(MAKE_DIRECTORY "${dir}")

  set(input_libraries)
  foreach(built_arch_root ${built_arch_roots})
    list(APPEND input_libraries ${built_arch_root}/${x})
  endforeach()

  execute_process(
      COMMAND
        lipo
        -create
        ${input_libraries}
        -o
        "@HUNTER_PACKAGE_INSTALL_PREFIX@/${x}"
      RESULT_VARIABLE
        lipo_result
      ERROR_VARIABLE
        lipo_error
  )
  if(NOT ${lipo_result} EQUAL 0)
    message(FATAL_ERROR "lipo execution failed: ${lipo_error}")
  endif()

  file(REMOVE ${input_libraries})
endforeach()

file(
    GLOB_RECURSE
    rest_files
    RELATIVE
    "${first_built_root}"
    ${first_built_root}/*
)

# All other files should be the same
foreach(x ${rest_files})
  set(etalon "${first_built_root}/${x}")
  foreach(arch ${ios_architectures})
    set(to_check "@multi_arch_install_root@/${arch}/${x}")
    file(
        DIFFERENT
        is_different
        FILES "${to_check}" "${etalon}"
    )
    if(is_different)
      message(FATAL_ERROR "Files differ: ${to_check} ${etalon}")
    endif()
  endforeach()
endforeach()

foreach(x ${rest_files})
  set(etalon "${first_built_root}/${x}")
  set(dst "@HUNTER_PACKAGE_INSTALL_PREFIX@/${x}")
  file(WRITE "${dst}" "") # for creating missing directories
  file(RENAME "${etalon}" "${dst}")
  foreach(arch ${ios_architectures})
    file(REMOVE "@multi_arch_install_root@/${arch}/${x}")
  endforeach()
endforeach()

# Check no files left (i.e. all binaries fused by lipo, all headers merged)
file(GLOB_RECURSE files_left "@multi_arch_install_root@/*")
string(COMPARE EQUAL "${files_left}" "" is_empty)
if(NOT is_empty)
  message(FATAL_ERROR "Unexpected files: ${files_left}")
endif()
